import java.awt.AWTException;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.WindowEvent;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import sun.audio.AudioPlayer;
import sun.audio.AudioStream;

public class GameWindow extends JFrame {

	private Image imageBuffer; // for double buffering

	MediaTracker mediaTracker = new MediaTracker(this);

	private Image imgShootable;

	private PixelGrabber pixelGrabberShootable;

	private Object shootPixels;

	private Image imgWalkable;

	private PixelGrabber pixelGrabberWalkable;

	private Object walkPixels;

	private Graphics imageBufferGraphics;

	static StickFigure me = new StickFigure();

	static Map gameMap = new Map();

	static ArrayList<Bullet> bullets = new ArrayList<Bullet>();

	static ArrayList<Bullet> deadBullets = new ArrayList<Bullet>();

	static Robot robot;

	char keyForward = 'w';

	char keyStrafeLeft = 'a';

	char keyBack = 's';

	char keyStrafeRight = 'd';

	char keyFire = ' ';

	GameWindow() {

		addWindowListener(new java.awt.event.WindowAdapter() {
			public void windowClosing(WindowEvent winEvt) {
				System.exit(0);
			}
		});

		try {
			robot = new Robot();
		} catch (AWTException e) {
			e.printStackTrace();
			System.exit(1);
		}

		addKeyListener(getKeyListener());

		this.setContentPane(getDrawingArea());
		this.setTitle("Stick Fury");

	}

	JPanel drawingArea;

	private JPanel getDrawingArea() {
		if (drawingArea == null) {
			drawingArea = new JPanel();

			int[] pixels = new int[16 * 16];
			Image image = Toolkit.getDefaultToolkit().createImage(
					new MemoryImageSource(16, 16, pixels, 0, 16));
			Cursor transparentCursor = Toolkit.getDefaultToolkit()
					.createCustomCursor(image, new Point(0, 0),
							"invisibleCursor");

			drawingArea.setCursor(transparentCursor);

			drawingArea.addMouseWheelListener(new MouseWheelListener() {

				public void mouseWheelMoved(MouseWheelEvent e) {
					int notches = e.getWheelRotation();
					if (notches < 0) {
						// wheel up
						gameMap.zoom -= 0.1; // zoom in
					} else {
						// wheel down
						gameMap.zoom += 0.1; // zoom out
					}
				}

			});

			drawingArea.addMouseListener(new MouseListener() {

				public void mouseClicked(MouseEvent mouseEvent) {
				}

				public void mouseEntered(MouseEvent mouseEvent) {
				}

				public void mouseExited(MouseEvent mouseEvent) {
				}

				public void mousePressed(MouseEvent mouseEvent) {
					me.firing = (mouseEvent.getButton() == 1);
				}

				public void mouseReleased(MouseEvent mouseEvent) {
					me.firing = !(mouseEvent.getButton() == 1);
				}
			});

			drawingArea.addMouseMotionListener(new MouseMotionListener() {

				public void mouseDragged(MouseEvent mouseEvent) {

					aim(mouseEvent);
				}

				public void mouseMoved(MouseEvent mouseEvent) {

					aim(mouseEvent);
				}

				private void aim(MouseEvent mouseEvent) {
					Point refPoint = new Point(drawingArea.getWidth() / 2,
							drawingArea.getHeight() / 2);

					double xDelta = mouseEvent.getX() - drawingArea.getWidth()
							/ 2;

					me.direction += (xDelta / 2);

					SwingUtilities.convertPointToScreen(refPoint, drawingArea);

					robot.mouseMove(refPoint.x, refPoint.y);
				}
			});
		}
		return drawingArea;
	}

	KeyListener keyListener;

	private KeyListener getKeyListener() {
		if (keyListener == null) {
			keyListener = new KeyListener() {

				public void keyPressed(KeyEvent ke) {

					if (ke.getKeyChar() == keyForward) {
						me.moveForward = true;
					}
					if (ke.getKeyChar() == keyStrafeLeft) {
						me.moveLeft = true;
					}
					if (ke.getKeyChar() == keyBack) {
						me.moveBackward = true;
					}
					if (ke.getKeyChar() == keyStrafeRight) {
						me.moveRight = true;
					}

					if (ke.getKeyChar() == keyFire) {
						me.firing = true;
					}

				}

				public void keyReleased(KeyEvent ke) {

					if (ke.getKeyChar() == keyForward) {
						me.moveForward = false;
					}
					if (ke.getKeyChar() == keyStrafeLeft) {
						me.moveLeft = false;
					}
					if (ke.getKeyChar() == keyBack) {
						me.moveBackward = false;
					}
					if (ke.getKeyChar() == keyStrafeRight) {
						me.moveRight = false;
					}

					if (ke.getKeyChar() == keyFire) {
						me.firing = false;
					}

				}

				public void keyTyped(KeyEvent ke) {
				}
			};
		}
		return keyListener;
	}

	public void start() {

		Runnable drawingLoop = new Runnable() {

			public void run() {
				// initialize buffer if it's not set yet
				if (imageBuffer == null) {
					imageBuffer = createImage(GameWindow.this.getWidth(),
							GameWindow.this.getHeight());
					imageBufferGraphics = imageBuffer.getGraphics();
				}

				imgWalkable = Toolkit.getDefaultToolkit().getImage(
						"mapWalk.gif");
				mediaTracker.addImage(imgWalkable, 0);
				try {
					mediaTracker.waitForID(0);
				} catch (InterruptedException e) {
					e.printStackTrace();
					System.exit(1);
				}
				mediaTracker.removeImage(imgWalkable);
				pixelGrabberWalkable = new PixelGrabber(imgWalkable, 0, 0,
						imgWalkable.getWidth(GameWindow.this), imgWalkable
								.getHeight(GameWindow.this), true);
				try {
					pixelGrabberWalkable.grabPixels();
				} catch (InterruptedException e1) {
					e1.printStackTrace();
					System.exit(1);
				}
				walkPixels = pixelGrabberWalkable.getPixels();
				imgShootable = Toolkit.getDefaultToolkit().getImage(
						"mapShoot.gif");
				mediaTracker.addImage(imgShootable, 0);
				try {
					mediaTracker.waitForID(2);
				} catch (InterruptedException e) {
					e.printStackTrace();
					System.exit(1);
				}
				mediaTracker.removeImage(imgShootable);
				pixelGrabberShootable = new PixelGrabber(imgShootable, 0, 0,
						imgShootable.getWidth(GameWindow.this), imgShootable
								.getHeight(GameWindow.this), true);
				try {
					pixelGrabberShootable.grabPixels();
				} catch (InterruptedException e1) {
					e1.printStackTrace();
					System.exit(1);
				}
				shootPixels = pixelGrabberShootable.getPixels();

				while (true) {
					draw();
					delay();
				}
			}

			private void draw() {
				Graphics g = GameWindow.this.getDrawingArea().getGraphics();

				// draw terrain to background buffer
				gameMap.drawTerrain(imageBufferGraphics);

				// draw player to background buffer
				me.draw(gameMap, imageBufferGraphics);

				try {
					ArrayList<Bullet> bulletsToDraw = (ArrayList<Bullet>) bullets
							.clone();

					for (Bullet bullet : bulletsToDraw) {
						if (bullet.age > 2) {
							bullet.draw(gameMap, imageBufferGraphics);
						}
					}
				} catch (Exception e) {

				}

				// move background buffer to forground (screen)
				g.drawImage(imageBuffer, 0, 0, GameWindow.this);

			}

			private void delay() {
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					e.printStackTrace();
					System.exit(1);
				}
			}

		};

		Runnable physicsLoop = new Runnable() {

			public void run() {
				while (true) {
					physics();
					delay();
				}
			}

			private void delay() {
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					e.printStackTrace();
					System.exit(1);
				}
			}

			private void physics() {

				gameMap.setPos(me.xPos, me.yPos);

				if (imgWalkable != null && walkPixels != null) {

					Primitives.Point nextPoint = me.getNextPoint();

					int[] walkPixelData = (int[]) walkPixels;
					int pixel = (int) walkPixelData[(int) nextPoint.y
							* imgWalkable.getWidth(GameWindow.this)
							+ (int) nextPoint.x];

					if (pixel == -1) {
						me.physics();
					}

				}

				deadBullets.clear();

				if (me.firing) {
					if ((System.currentTimeMillis() - me.gun.lastFiringTime) > me.gun.ticksPerBullet) {
						double direction = me.direction
								+ (Math.random() * me.gun.recoil - me.gun.recoil / 2);
						bullets.add(new Bullet(me.xPos, me.yPos, direction,
								me.gun.pixelsPerTick));
						me.gun.recoil += me.gun.staticBulletRecoil;
						me.gun.recoil *= me.gun.dynamicBulletRecoil;
						me.gun.lastFiringTime = System.currentTimeMillis();

						InputStream in;
						try {
							in = new FileInputStream("bang.wav");
							AudioStream as = new AudioStream(in);
							AudioPlayer.player.start(as);
						} catch (FileNotFoundException e) {
							e.printStackTrace();
						} catch (IOException e) {
							e.printStackTrace();
						}

					}
				}

				if (me.gun.recoil > 0) {
					me.gun.recoil -= me.gun.staticRecoilRecovery;
					me.gun.recoil *= me.gun.dynamicRecoilRecovery;
				}

				me.gun.recoil = Math.min(90, me.gun.recoil);

				for (Bullet bullet : bullets) {
					bullet.physics();
					if (bullet.age > 100) {
						deadBullets.add(bullet);
						continue;
					}

					if (imgShootable != null && shootPixels != null) {

						int[] shootPixelData = (int[]) shootPixels;
						int pixel = (int) shootPixelData[(int) bullet.yPos
								* imgShootable.getWidth(GameWindow.this)
								+ (int) bullet.xPos];

						if (pixel != -1) {
							bullet.age += 100;
						}

					}

				}

				bullets.removeAll(deadBullets);
			}

		};

		Thread physicsThread = new Thread(physicsLoop);
		Thread drawingThread = new Thread(drawingLoop);

		this.setBounds(0, 0, 500, 500);
		me.xPos = 550;
		me.yPos = 550;

		this.setVisible(true);

		physicsThread.start();
		drawingThread.start();

	}

	public static void main(String[] args) {

		GameWindow gw = new GameWindow();
		gw.start();

	}

}
